/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_zml.htm
 *
 * Zhiqim Zml is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.zml;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import org.zhiqim.kernel.model.maps.HashMapSO;
import org.zhiqim.kernel.model.maps.LinkedMapSV;
import org.zhiqim.kernel.util.Arrays;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.zml.loader.ClassZmlLoader;
import org.zhiqim.zml.loader.FileZmlLoader;
import org.zhiqim.zml.statement._Function;
import org.zhiqim.zml.statement._Include;
import org.zhiqim.zml.statement._Var;

/**
 * ZhiqimML引擎主程序<br><br>
 * 
 * 1、设置引擎参数，如加载器、编码等<br>
 * 2、加载全局函数定义文件。<br>
 * 3、设置共享变量。<br>
 * 
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class ZmlEngine extends ZmlPredefinded implements ZmlConstants
{
    //ZML引擎基本参数
    private ZmlVarNotice notice;
    private String encoding;
    private int maxIdleTime;
    private int maxKeepTime;
    private boolean isAscQuery;
    private String[] patterns;
    
    //ZML引擎加载器和全局变量
    private ZmlLoader loader;
    private final LinkedMapSV<ZmlLoader> cLoaderMap;
    private final HashMapSO globalMap;
    
    //ZML引擎配置ZML（最后修改时间、函数表和变量表）
    private final LinkedHashMap<ConfigKey, Long> fileMap;
    private final LinkedHashMap<ConfigKey, LinkedMapSV<_Function>> functionMap;
    private final LinkedHashMap<ConfigKey, LinkedMapSV<_Var>> variableMap;
    
    /** 默认ZML引擎构造方法，编码格式默认为UTF-8 */
    public ZmlEngine()
    {
        this(_UTF_8_);
    }
    
    /**
     * ZML引擎构造方法，传入编码格式
     * 
     * @param encoding      ZML通用的编码格式
     */
    public ZmlEngine(String encoding)
    {
        this.encoding = encoding;
        this.patterns = ZML_PATTERN_DEFAULT;
        
        this.cLoaderMap = new LinkedMapSV<>();
        this.globalMap = new HashMapSO();
        
        this.fileMap = new LinkedHashMap<>();
        this.functionMap = new LinkedHashMap<>();
        this.variableMap = new LinkedHashMap<>();
    }
    
    /**************************************************************************/
    //ZML引擎基本参数
    /**************************************************************************/
    
    public void setZmlVarNotice(ZmlVarNotice notice)
    {
        this.notice = notice;
    }
    
    public void setAscQuery(boolean isAscQuery)
    {
        this.isAscQuery = isAscQuery;
    }
    
    public void setEncoding(String encoding)
    {
        this.encoding = encoding;
    }
    
    public void setMaxIdleTime(int maxIdleTime)
    {
        this.maxIdleTime = maxIdleTime;
    }

    public void setMaxKeepTime(int maxKeepTime)
    {
        this.maxKeepTime = maxKeepTime;
    }
    
    public void setPatterns(String[] patterns)
    {
        this.patterns = patterns;
    }
    
    public void setPatterns(String patterns)
    {
        this.patterns = Arrays.toStringArray(patterns);
    }
    
    public String getEncoding()
    {
        return encoding;
    }
    
    public int getMaxIdleTime()
    {
        return maxIdleTime;
    }

    public int getMaxKeepTime()
    {
        return maxKeepTime;
    }
    
    public String[] getPatterns()
    {
        return patterns;
    }
    
    /**************************************************************************/
    //ZML引擎加载器和全局变量
    /**************************************************************************/
    
    /**
     * 设置ZML加载器，如果是默认的类加载器和文件加载器，请使用setClassZmlLoader/setFileZmlLoader方法
     * 
     * @param loader        ZML加载器
     */
    public void setZmlLoader(ZmlLoader loader)
    {
        this.loader = loader;
    }
    
    /**
     * 设置类ZML加载器
     * 
     * @param clazz         给定一个能访问到ZML文件的类，防止跨ClassLoader加载失败
     * @param pathPrefix    ZML文件路径前缀，格式为/org/zhiqim/example
     */
    public void setClassZmlLoader(Class<?> clazz, String pathPrefix)
    {
        this.loader = new ClassZmlLoader(this, clazz, pathPrefix);
    }
    
    /**
     * 设置文件夹加载器
     * 
     * @param dir           ZML文件根目录，如new File("./resource");
     * @throws IOException  检查文件根目录异常
     */
    public void setFileZmlLoader(File dir) throws IOException
    {
        this.loader = new FileZmlLoader(this, dir.getCanonicalPath());
    }
    
    /**
     * 增加组件ZML加载器
     * 
     * @param clazz         给定一个能访问到ZML文件的类，防止跨ClassLoader加载失败
     * @param pathPrefix    ZML文件路径前缀，格式为/org/zhiqim/example
     */
    public void addComponentZmlLoader(Class<?> clazz, String pathPrefix)
    {
        this.cLoaderMap.put(pathPrefix, new ClassZmlLoader(this, clazz, pathPrefix));
    }
    
    public void addComponentZmlLoader(String componentPath, ZmlLoader loader)
    {
        this.cLoaderMap.put(componentPath, loader);
    }

    public ZmlLoader getLoader(ConfigKey ck)
    {
        return getLoader(ck.configPath, ck.componentPath);
    }
    
    public ZmlLoader getLoader(String contextPath, String componentPath)
    {
        return (componentPath==null)?this.loader:this.cLoaderMap.get(componentPath);
    }
    
    /**
     * 增加全局变量
     * 
     * @param name  变量名
     * @param obj   变量值
     */
    public void addGlobalVariable(String name, Object obj)
    {
        globalMap.put(name, obj);
    }
    
    public Object getGlobalVariable(String key)
    {
        return globalMap.get(key);
    }

    public HashMapSO getGlobalMap()
    {
        return globalMap;
    }
    
    /**************************************************************************/
    //ZML引擎配置ZML（最后修改时间、函数表和变量表）
    /**************************************************************************/
    
    /**
     * 添加上下文配置ZML
     * 
     * @param configPath 上下文配置ZML路径
     */
    public void addConfigZml(String configPath)
    {
        Asserts.as(this.loader != null?null:"未设置加载器上不支持添加上下文配置ZML");
        this.loadConfigZml(new ConfigKey(configPath, null));
    }
    
    /**
     * 添加组件上下文配置ZML，带组件路径
     * 
     * @param configPath        上下文配置ZML路径
     * @param componentPath     组件路径
     */
    public void addConfigZml(String configPath, String componentPath)
    {
        Asserts.as(this.cLoaderMap.containsKey(componentPath)?null:"未设置该组件加载器上不支持添加组件上下文配置ZML");
        this.loadConfigZml(new ConfigKey(configPath, componentPath));
    }

    /** 检查当前配置更新 */
    public void chkCurConfigModified()
    {
        for (Entry<ConfigKey, Long> entry : fileMap.entrySet())
        {
            if (entry.getKey().componentPath != null)
                continue;
            
            try
            {
                long lastModified = getConfigLastModified(entry.getKey());
                if (lastModified != entry.getValue())
                    loadConfigZml(entry.getKey());
            }
            catch (Exception e)
            {
                throw Asserts.exception("加载上下文ZML["+entry.getKey().configPath+"]异常", e);
            }
        }
    }
    
    /** 检查所有配置更新 */
    private void chkAllConfigModified()
    {
        for (Entry<ConfigKey, Long> entry : fileMap.entrySet())
        {
            try
            {
                long lastModified = getConfigLastModified(entry.getKey());
                if (lastModified != entry.getValue())
                    loadConfigZml(entry.getKey());
            }
            catch (Exception e)
            {
                throw Asserts.exception("加载上下文ZML["+entry.getKey().configPath+"]异常", e);
            }
        }
    }
    
    /** 获取配置ZML最后修改时间 */
    private long getConfigLastModified(ConfigKey ck) throws Exception
    {
        ZmlLoader loader = getLoader(ck);
        return loader.getLastModified(ck.configPath);
    }
    
    /** 获取配置ZML里的函数表 */
    public LinkedMapSV<_Function> getFunctionMap(String contextPath, String componentPath)
    {
        return functionMap.get(new ConfigKey(contextPath, componentPath));
    }
    
    /** 获取配置ZML里的变量表 */
    public LinkedMapSV<_Var> getVarMap(String contextPath, String componentPath)
    {
        return variableMap.get(new ConfigKey(contextPath, componentPath));
    }
    
    /** 查找配置ZML中函数语句 */
    public _Function getFunction(String name) throws Exception
    {
        if (loader == null)
            throw new Exception("未定义加载器，不支持获取函数调用");
        
        //先检查上下文文件更新标志
        chkAllConfigModified();
        
        if (isAscQuery)
        {//按顺序查找对应的函数
            for (LinkedMapSV<_Function> fMap : functionMap.values())
            {
                _Function _function = fMap.get(name);
                if (_function != null)
                    return _function;
            }
        }
        else
        {//按倒序查找对应的函数
            _Function _function = null;
            for (LinkedMapSV<_Function> fMap : functionMap.values())
            {
                if (fMap.containsKey(name))
                    _function = fMap.get(name);
            }
            
            if (_function != null)
                return _function;
        }
        
        //如果没找到抛异常
        throw new Exception("加载上下文ZML失败，不支持获取函数");
    }
    
    /** 查找配置ZML中变量语句 */
    public _Var getVar(String name)
    {
        if (loader == null)
            return null;
        
        //先检查上下文文件更新标志
        chkAllConfigModified();
        
        if (isAscQuery)
        {//按顺序查找对应的变量
            for (LinkedMapSV<_Var> vMap : variableMap.values())
            {
                _Var _var = vMap.get(name);
                if (_var != null)
                    return _var;
            }
        }
        else
        {//按倒序查找对应的变量
            _Var _var = null;
            for (LinkedMapSV<_Var> vMap : variableMap.values())
            {
                if (vMap.containsKey(name))
                    _var = vMap.get(name);
            }
            
            if (_var != null)
                return _var;
        }
        
        //如果没找到返回null
        return null;
    }
    
    /**************************************************************************/
    //指定路径判断和查找ZML信息 has & get
    /**************************************************************************/
    
    /**
     * 判断是否存在ZML信息
     * 
     * @param path  ZML相对路径
     * @return      =true表示存在，=false表示不存在
     */
    public boolean hasZml(String path)
    {
        Asserts.as(this.loader != null?null:"未设置加载器上不支持增通过引擎获取ZML");
        
        try
        {
            //优先本地查找
            if (loader.hasZml(path))
                return true;
            
            //再到组件中查找
            List<ZmlLoader> list = new ArrayList<>(cLoaderMap.values());
            for (int i=list.size()-1;i>=0;i--)
            {
                ZmlLoader ld = list.get(i);
                if (ld.hasZml(path))
                    return true;
            }
            
            return false;
        }
        catch (Exception e)
        {
            throw Asserts.exception("加载ZML文件["+path+"]异常", e);
        }
    }
    
    /**
     * 判断是否存在ZML信息
     * 
     * @param path  ZML相对路径
     * @return      存在返回加载器，不存在返回false
     */
    public ZmlLoader getZmlLoader(String path)
    {
        Asserts.as(this.loader != null?null:"未设置加载器上不支持增通过引擎获取ZML");
        
        try
        {
            //优先本地查找
            if (loader.hasZml(path))
                return loader;
            
            //再到组件中查找
            List<ZmlLoader> list = new ArrayList<>(cLoaderMap.values());
            for (int i=list.size()-1;i>=0;i--)
            {
                ZmlLoader ld = list.get(i);
                if (ld.hasZml(path))
                    return ld;
            }
            
            return null;
        }
        catch (Exception e)
        {
            throw Asserts.exception("加载ZML文件["+path+"]异常", e);
        }
    }
    
    /**
     * 获取ZML信息
     * 
     * @param path          ZML相对路径
     * @return              ZML信息
     * @throws IOException  读取ZML文件时产生的异常
     */
    public Zml getZml(String path) throws FileNotFoundException
    {
        Asserts.as(this.loader != null?null:"未设置加载器上不支持增通过引擎获取ZML");
        
        try
        {
            //优先本地查找
            Zml zml = loader.loadZml(path);
            if (zml != null)
                return zml;
            
            //再到组件中查找
            List<ZmlLoader> list = new ArrayList<>(cLoaderMap.values());
            for (int i=list.size()-1;i>=0;i--)
            {
                ZmlLoader ld = list.get(i);
                zml = ld.loadZml(path);
                if (zml != null)
                    return zml;
            }
        }
        catch (Exception e)
        {
            throw Asserts.exception("加载ZML文件["+path+"]异常", e);
        }
        
        throw Asserts.notFound("未找到ZML文件["+path+"]");
    }
    
    /**************************************************************************/
    //加载配置ZML
    /**************************************************************************/
    
    /** 加载配置ZML */
    private void loadConfigZml(ConfigKey mk)
    {
        String configPath = mk.configPath;
        String componentPath = mk.componentPath;
        
        //1.1 先加载ZML
        Zml zml = null;
        try
        {
            ZmlLoader theLoader = componentPath==null?this.loader:this.cLoaderMap.get(componentPath);
            zml = theLoader.loadZml(configPath);
            if (zml == null)
                return;//未找到不加载
                
            //修改ZML文件的最后修改时间
            fileMap.put(mk, zml.getLastModified());
        }
        catch (Exception e)
        {
            throw Asserts.exception("加载上下文ZML["+configPath+"]异常", e);
        }
        
        //1.2 从ZML中找到函数列表，变量列表和包含列表
        List<_Function> funcList = new ArrayList<>();
        List<_Var> varList = new ArrayList<>();
        List<_Include> includeList = new ArrayList<>();
        
        try
        {
            StatementParser.parseContextZml(zml, funcList, varList, includeList);
        }
        catch (Exception e)
        {
            throw Asserts.exception("加载上下文ZML["+configPath+"]异常", e);
        }
    
        //2.1 找到上下文对应的全局函数表
        LinkedMapSV<_Function> funcMap = null;
        synchronized (functionMap)
        {
            LinkedMapSV<_Function> fMap = functionMap.get(mk);
            if (fMap == null)
            {
                fMap = new LinkedMapSV<_Function>();
                functionMap.put(mk, fMap);
            }
            
            funcMap = fMap;
        }
        
        //2.2 把ZML中的函数表加入到全局函数表中
        synchronized (funcMap)
        {
            try
            {
                funcMap.clear();
                for (_Function func : funcList)
                {
                    String name = func.getName();
                    if (funcMap.containsKey(name))//一个上下文文件定义全局函数不允许重复，多文件之间支持，按优先顺序读取
                        throw Asserts.exception("找到上下文ZML文件["+configPath+"]中全局函数["+name+"]有重复");
                    
                    funcMap.put(name, func);
                }
                
            }
            catch (Exception e)
            {
                throw Asserts.exception("加载上下文ZML["+configPath+"]异常", e);
            }
        }
        
        //3.1 找到上下文对应的全局变量表
        LinkedMapSV<_Var> varMap = null;
        synchronized (variableMap)
        {
            LinkedMapSV<_Var> vMap = variableMap.get(mk);
            if (vMap == null)
            {
                vMap = new LinkedMapSV<_Var>();
                variableMap.put(mk, vMap);
            }
            
            varMap = vMap;
        }
        
        //3.2 把ZML中的变量表并加入到全局变量表中
        synchronized (varMap)
        {
            try
            {
                varMap.clear();
                for (_Var var : varList)
                {
                    String name = var.getVariableName();
                    if (varMap.containsKey(name))//一个上下文文件定义全局变量不允许重复，多文件之间支持，按优先顺序读取
                        throw Asserts.exception("找到上下文ZML文件["+configPath+"]中全局变量["+name+"]有重复");
                    
                    varMap.put(name, var);
                }
                
                if (notice != null)
                {//变量更新通知
                    notice.doUpdateVariable(zml, configPath, componentPath, varMap);
                }
            }
            catch (Exception e)
            {
                throw Asserts.exception("加载上下文ZML["+configPath+"]异常", e);
            }
        }
        
        //4 递归加载里面包含表的函数和变量
        try
        {
            ZmlVariable variable = new ZmlVariable();
            for (_Include include : includeList)
            {
                String path = include.getIncludePath(variable);
                loadConfigZml(new ConfigKey(path, componentPath));
            }
        }
        catch (Exception e)
        {
            throw Asserts.exception("加载上下文ZML["+configPath+"]异常", e);
        }
    }
    
    /**************************************************************************/
    //ZML引擎配置键（配置路径和组件路径组合成唯一）
    /**************************************************************************/
    
    private class ConfigKey
    {
        private String configPath;
        private String componentPath;
        
        public ConfigKey(String contextPath, String componentPath)
        {
            this.configPath = contextPath;
            this.componentPath = componentPath;
        }

        @Override
        public int hashCode()
        {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((componentPath == null) ? 0 : componentPath.hashCode());
            result = prime * result + ((configPath == null) ? 0 : configPath.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            
            ConfigKey other = (ConfigKey)obj;
            if (componentPath == null)
            {
                if (other.componentPath != null)
                    return false;
            }
            else if (!componentPath.equals(other.componentPath))
                return false;
            if (configPath == null)
            {
                if (other.configPath != null)
                    return false;
            }
            else if (!configPath.equals(other.configPath))
                return false;
            return true;
        }
    }
}
